package br.ufpr.inf.escel.brain.variator;

import br.ufpr.inf.escel.brain.parsers.Main;
import br.ufpr.inf.escel.brain.model.DadoTeste;
import br.ufpr.inf.escel.brain.model.Individual;
import br.ufpr.inf.escel.brain.operators.Operadores;
import br.ufpr.inf.escel.brain.operators.OperadoresFactory;
import br.ufpr.inf.escel.brain.util.NumeroUtils;
import br.ufpr.inf.escel.consts.Constantes;
import br.ufpr.inf.escel.manager.util.Arquivo;
import br.ufpr.inf.escel.testing.Console;
import br.ufpr.inf.escel.testing.model.Cut;
import br.ufpr.inf.escel.util.Writer;
import edu.gmu.javalution.pfronts.DebParetoFronts;
import edu.gmu.javalution.pfronts.EpsilonDoubleArrayComparator;
import edu.gmu.javalution.pfronts.IParetoFront;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/** Handles the problem-specific behavior of the global population.
 * Contains all problem-specific parameters and implements a method to set them. Implements methods
 * to initialize a global population, to variate the individuals (recombination and mutation),
 * and to test for the stopping criterion of the EA.
 * Provides functions to perform recombination and mutation.
 *
 * @author Tamara Ulrich
 * @version 1.0
 */
class Populacao extends PopulationAbstract {

    /** tipo de operador utilizado **/
    private Operadores operadores;
    /** fronteira de pareto **/
    private IParetoFront<double[]> fronts = new DebParetoFronts<double[], EpsilonDoubleArrayComparator>(new EpsilonDoubleArrayComparator(), 5);
    private Cut cut;

    public Populacao() {
    }

    /** Initializes the population with <code>alpha</code> individuals that have random decision space representations. */
    void initialize() {
        //copia o programa para uma nova pasta
        Console.getInstance().copy(programaTeste, pastaCut, novaPastaCut);

        operadores = OperadoresFactory.getInstance(tipoPrograma);

        cut = Cut.getCut(tipoPrograma);
        cut.setNovoPath(novaPastaCut);
        cut.setPrograma(programaTeste);
        cut.setCriterio(criterio);

        for (int i = 0; i < alpha; i++) {
            Individual ind = Main.createIndividualBy(tipoPrograma);
            ind.setId(i);
            ind.initTools(cut);
            ind.eval();
            numAvaliacoes++;
            globalPopulation.add(ind);
        }
        writePopulation();
        Variator.debugPrint("Initial population constructed.");
    }

    /** Performs recombination and mutation on the individuals with the given indices.
     * First, the parents are recombined with <code>recombinationProbability</code>.
     * Then, they are mutated with <code>mutationProbability</code>.
     *
     * @param offspring the indices of the individuals to be variated
     */
    public void performVariation(int[] offspring) {
        // recombination
        int k;
        if (offspring.length % 2 != 1) { // equal number of parents

            k = offspring.length;
        } else {
            k = offspring.length - 1;
        }

        //cruzamento
        for (int i = 0; i < k; i++) {
            Individual individual = globalPopulation.get(offspring[i]);

            List<DadoTeste> selecionados = new ArrayList<DadoTeste>();

            while (selecionados.size() <= individual.getCapacidade() / 3) {
                selecionados.add(operadores.selecionar(individual));
            }

            List<DadoTeste> embaralhados = new ArrayList<DadoTeste>(selecionados);
            Collections.shuffle(embaralhados);

            //cruzamento
            for (int j = 0; j < selecionados.size(); j++) {
                if (Variator.randomGenerator.nextDouble() <= recombinationProbability) {
                    DadoTeste dt = operadores.cruzar(selecionados.get(j), embaralhados.get(j));
                    individual.update(selecionados.get(j), dt);
                    numCruzamento++;
                }
                globalPopulation.set(offspring[i], individual);
            }

            //mutacao
            for (int j = 0; j < selecionados.size(); j++) {
                if (Variator.randomGenerator.nextDouble() <= mutationProbability) {
                    DadoTeste dt = operadores.mutar(selecionados.get(j));
                    individual.update(selecionados.get(j), dt);
                    numMutacao++;
                }
                globalPopulation.set(offspring[i], individual);
            }
        }

        //relacionamento
        for (int i = 0; i < k; i += 2) {
            if (Variator.randomGenerator.nextDouble() <= relationshipProbabily) {
                numRelacionamento++;
                Individual ind1 = globalPopulation.get(offspring[i]);
                Individual ind2 = globalPopulation.get(offspring[i + 1]);
                Individual[] novosInds = operadores.relacionar(ind1, ind2);

                globalPopulation.set(offspring[i], novosInds[1]);
                globalPopulation.set(offspring[i + 1], novosInds[0]);
            }
        }
    }

    /** Tests whether the current generation is the last one.
     *
     * @return true if the EA should finish, else false
     */
    boolean isFinished() {

        boolean isFinished = Variator.population.generation >= Variator.population.maximumGenerations + 1;

        if (isFinished) {
            for (int i = 0; i < globalPopulation.size(); i++) {
                Individual ind = globalPopulation.get(i);
                fronts.add(new double[]{ind.getObjectiveSpace()[0], ind.getObjectiveSpace()[1]});
            }
        }

        return isFinished;
    }

    /** Tests whether the parameters in the parameter file have reasonable values. */
    void testParam() {
        // test if mu and lambda from config file are the same, otherwise exit PISA
        if ((mu != lambda)) {
            System.err.println("ERROR: wsnNodePlacer::Launcher:main: mu and lambda read from config file are not the same!");
            System.exit(1);
        }

        // test if dim equals 2, the only allowed setting for this problem
        if (dim != 2) {
            System.err.println("ERROR: wsnNodePlacer::Launcher:main: For this problem, number of objectives needs to be 2 not " + dim + "!");
            System.exit(1);
        }
    }

    /** Writes all individuals of the population to the output file. The name of the output file is specified in the parameter
     * file. First, the number of individuals is printed. Then, each line in the output file corresponds to an individual, where the first elements indicate the objective
     * space values and the remaining elements give the decision space representation. Finally, an <code>END</code> tag is written.
     */
    void writeOutput() {
        try {
            PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(Variator.population.outputFileName)), true);
            writer.println(Integer.toString((Variator.population.globalPopulation.size() - Variator.population.freeIdentities.size())));
            Individual currentIndividual;

            for (int i = 0; i < Variator.population.globalPopulation.size(); i++) {
                if (!Variator.population.freeIdentities.contains(i)) {
                    writer.print(i + "\t");
                    currentIndividual = Variator.population.globalPopulation.get(i);
                    for (int j = 0; j < Variator.population.dim; j++) {
                        writer.printf("%10.10f\t", currentIndividual.getObjectiveSpace()[j]);
                    }
                    writer.println("");
                }
            }
            writer.println("END");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Imprime a fronteira resultante do algoritmo
     */
    public void writePareto() {
        List<Double> obj0 = new ArrayList<Double>();
        List<Double> obj1 = new ArrayList<Double>();
        for (int i = 0; i < fronts.getNumberOfFronts(); i++) {
            Collection<double[]> objetivos = fronts.getFront(i);
            for (double[] obj : objetivos) {
                obj0.add(obj[0]);
                obj1.add(obj[1]);
                Writer.it(Arrays.toString(obj));
            }
            Writer.it("\n");
        }
    }

    /**
     * Escreve na tela a população
     */
    public void writePopulation() {
        for (Individual ind : globalPopulation) {
//            Writer.it("INDIVIDUO " + ind.getId() + " " + ind.getScore());
//            for (DadoTeste dadoTeste : ind.getDadosTeste()) {
//                Writer.it("\tTESTCASE_" + dadoTeste.getId() + " " + NumeroUtils.toDecimal(dadoTeste.getFitness()) + " [" + dadoTeste.toString() + "] ");
//            }

            if (null == ind) {
                System.out.println("individuo nulo!!");
            } else if (ind.getObjectiveSpace() == null) {
                System.out.println("objetivos nulos!!!");
            } else {
                //tenta adicionar cada individuo na fronteira
                fronts.add(new double[]{ind.getObjectiveSpace()[0], ind.getObjectiveSpace()[1]});
            }
        }
    }

    /**
     * Imprime o estado atual das variaveis do processo evolutivo
     */
    public void writeVariablesState() {

        System.out.println("\nNumero de Cruzamentos: " + numCruzamento);
        System.out.println("Numero de Mutações: " + numMutacao);
        System.out.println("Numero de Relacionamentos: " + numRelacionamento);
        System.out.println("Numero de Avaliações: " + numAvaliacoes);
        
        StringBuffer naodominados = new StringBuffer();

        for (int i = 0; i < fronts.getNumberOfFronts(); i++) {

            Collection<double[]> fronteiras = fronts.getFront(i);
            naodominados.append("Fronteira atual (" + fronteiras.size() + ")\n");

            for (double[] solucoes : fronteiras) {
                naodominados.append(
                        NumeroUtils.toDecimal(solucoes[0]) +
                        "\t" +
                        NumeroUtils.toDecimal(solucoes[1]) + "\n");
            }
        }
        System.out.println(naodominados.toString());
        Arquivo.escreva("fronteira", Constantes.ARQUIVO_FRONTEIRAS, naodominados.toString());
    }
}
